/*********************************************************************************
 *      Copyright:  (C) 2023 Lin XinCheng
 *                  All rights reserved.
 *
 *       Filename:  led_gpio.c
 *    Description:  This file for led driver learning.
 *                 
 *        Version:  1.0.0(2023年07月18日)
 *         Author:  Lin XinCheng <1481155734@qq.com>
 *      ChangeLog:  1, Release initial version on "2023年07月18日 13时57分42秒"
 *                 
 ********************************************************************************/

/* 编写驱动程序分为三个部分
 *	1.编写平台设备驱动的入口和出口函数
 *	
 *	2.编写平台设备的.probe函数中实现字符设备的注册和LED灯的初始化
 *
 *	3.编写字符设备的函数集，实现open和write函数
 * */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>

#define DEV_NAME "my_led"   //最后在/dev路径下的设备名称，应用层open的字符串名

/* Set the LED dev major number*/
//#define DEV_MAJOR  79*/
#ifndef DEV_MAJOR 
#define DEV_MAJOR 0
#endif

#define PLATDRV_MAGIC	0x60	//魔术字
#define LED_OFF			_IO (PLATDRV_MAGIC, 0X18)
#define LED_ON			_IO (PLATDRV_MAGIC, 0X19)

static	int	dev_major = DEV_MAJOR;	//主设备号

struct led_device{
	dev_t				devid;		//设备号
	struct cdev			cdev;		//cdev结构体
	struct class		*class;		//定义一个class类
	struct device		*device;	//设备
	struct device_node	*node;		//led设备节点
	struct gpio_desc	*led_gpio;	//led灯gpio描述符
};

struct led_device led_dev; //led设备

/* 字符设备操作函数集，write函数 */

static int led_open(struct inode *inode,struct file *file)
{
	file->private_data = &led_dev; //设置私有数据
	printk(KERN_DEBUG "/dev/led%d opened.\n",led_dev.devid);
	return 0;
}

static int led_release(struct inode *inode,struct file *file)
{
	//file->private_data = &led_dev; //设置私有数据
	printk(KERN_DEBUG "/dev/led%d opened.\n",led_dev.devid);
	return 0;
}

static void print_led_help(void)
{
	printk("Follow is the ioctl() command for LED driver:\n");
	printk("Turn LED on command		:%u\n",LED_ON);
	printk("Turn LED off command	:%u\n",LED_OFF);
}

static long led_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
	switch(cmd)
	{
		case LED_OFF:
			gpiod_set_value(led_dev.led_gpio,0);
			break;
		case LED_ON:
			gpiod_set_value(led_dev.led_gpio,1);
			break;
		default:
			printk("%s driver don't support ioctl command=%d\n",DEV_NAME,cmd);
			print_led_help();
			return -EINVAL;
	}
	return 0;
}

/* 字符设备操作函数集 */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open  = led_open,
	.release = led_release,
	.unlocked_ioctl = led_ioctl,
};

/* probe函数实现字符设备的注册和LED灯的初始化 */
static int led_probe(struct platform_device *pdev)
{
	int result = 0 ;  //用于保存申请设备号的结果
	printk("\t match successed\n");

	memset(&led_dev, 0, sizeof(led_dev));

	/* 获取led的设备节点 */
	led_dev.led_gpio = gpiod_get(&pdev->dev, "led", 0 ); //请求gpio,有请求一定要有释放，否则模块下一次安装将请求失败
	if(IS_ERR(led_dev.led_gpio))
	{
		printk("gpiod request failure\n");
		return PTR_ERR(led_dev.led_gpio);
	}

	result = gpiod_direction_output(led_dev.led_gpio, 0); //设置GPIO的方向为输出，默认电平为低
	if( 0 != result )
	{
		printk("gpiod drection output set failure\n");
		return result;
	}

	/* ---------------------注册 字符设备部分----------------- */
	/* 1.分配主次设备号，这里即支持静态指定，也支持动态申请 */
	if( 0 != dev_major) //static 
	{
		led_dev.devid = MKDEV(dev_major, 0);
		result = register_chrdev_region(led_dev.devid, 1, DEV_NAME); /* /proc/devices/my_led */
	}
	else{
		result = alloc_chrdev_region(&led_dev.devid, 0, 1,DEV_NAME); /* 动态申请字符设备号 */
		dev_major = MAJOR(led_dev.devid);	/* 获取主设备号 */
	}

	/* 自动分配设备号失败 */
	if( result < 0 )
	{
		printk("%s driver can't get major %d",DEV_NAME, dev_major);
		return result;
	}
	printk("%s driver use major %d\n",DEV_NAME, dev_major);

	/* 2.分配cdev结构体，绑定主次设备号、fops到cdev结构体中，并注册给Linux内核 */
	led_dev.cdev.owner = THIS_MODULE;  /* .owner这表示谁拥有你这个驱动程序 */
	cdev_init(&(led_dev.cdev), &led_fops);  /* 初始化cdev,把fops添加进去 */
	result = cdev_add(&(led_dev.cdev),led_dev.devid, 1); /* 注册给内核,设备数量1个 */

	if( 0!=result)
	{
		printk(" %s driver can't register cdev:result=%d\n",DEV_NAME,result);
		goto ERROR;
	}
	printk(" %s driver use major %d\n",DEV_NAME, dev_major);

	//3.创建类，驱动中进行节点创建
	led_dev.class = class_create(THIS_MODULE, DEV_NAME); /* sys/class/my_led创建类 */
	if(IS_ERR(led_dev.class))
	{
		printk("%s driver create class failure\n",DEV_NAME);
		result = -ENOMEM;
		goto ERROR;
	}

	//4.创建设备
	led_dev.device = device_create(led_dev.class, NULL, led_dev.devid, NULL, DEV_NAME); /* /dev/my_led创建设备节点 */
	if(IS_ERR(led_dev.device)){
		result = -ENOMEM;  //返回错误码,应用空间strerror查看
		goto ERROR;
	}

	return 0;

ERROR:
	printk(KERN_ERR" %s driver installed failure.\n",DEV_NAME);
	cdev_del(&(led_dev.cdev));  //删除字符设备
	unregister_chrdev_region(led_dev.devid, 1); //释放主次设备号
	return result ;
}

static int led_remove(struct platform_device *pdev)
{
	gpiod_set_value(led_dev.led_gpio, 0);  //低电平关灯
	gpiod_put(led_dev.led_gpio); //释放gpio

	cdev_del(&(led_dev.cdev)); //删除cdev
	unregister_chrdev_region(led_dev.devid, 1); //释放设备号
	device_destroy(led_dev.class, led_dev.devid); //注销设备
	class_destroy(led_dev.class);	//注销类
	
	return 0;
}

/* ------第一部分------*/
static const struct of_device_id leds_match_table[] = {
	{.compatible = "my-gpio-leds"},
	{/* sentinel */},
};

MODULE_DEVICE_TABLE(of, leds_match_table);

/* 定义平台驱动结构体 */
static struct platform_driver gpio_led_driver = {
	.probe	= led_probe, /*驱动安装时会执行勾子函数*/
	.remove = led_remove, //驱动卸载程序
	.driver	={
		.name	=	"my_led",
		.owner	=	THIS_MODULE,
		.of_match_table = leds_match_table,
	},
};

/*  ------第二部分-------*/
/* 驱动初始化函数 */
static int __init platdrv_led_init(void)
{
	int rv = 0;

	rv = platform_driver_register(&gpio_led_driver);  //注册platform的led驱动
	if( rv )
	{
		printk(KERN_ERR "%s ,%d:Can't register paltform driver %d\n",__FUNCTION__,__LINE__,rv);
		return rv;
	}
	printk("Regist LED platform driver successfully!\n");
	return 0;
}

/* ---------第三部分--------- */
/* 驱动注销函数 */
static void __exit platdrv_led_exit(void)
{
	printk("%s():%d remove led platform_device\n",__FUNCTION__,__LINE__);
	platform_driver_unregister(&gpio_led_driver); //卸载驱动
}

module_init(platdrv_led_init);
module_exit(platdrv_led_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("LinXinCheng");

